ApexとTerraformでCloudWatch Events Schedule x Lambda x SNS を設定する
はじめに
こんにちは、中山です。
以前ApexとTerraformを利用してCloudWatch Events経由で起動するLambda関数のデプロイ方法を書きました。
CloudWatch Eventsには特定Event経由でのLambda関数の起動以外に、定期的にLambda関数を起動する機能があります。今回のエントリではこの設定方法をご紹介します。また、Apexの知見が多少深まったのでその辺りも触れたいと思います。
利用するApexとTerraformのバージョンは以下の通りです。
Tool | Version |
---|---|
Apex | v0.10.2 |
Terraform | v0.16.0 |
利用するLambda関数
今回利用するLambda関数はijin/check_lambda_capacityです。詳細は作者の方のブログに詳しいですが、アップロードしたLambda関数のデプロイパッケージの合計サイズを集計することが可能です。Lambda関数にはRegionあたりのアップロード可能なデプロイパッケージに対して制限があります。この制限を超えてデータをアップロードすることはできません。
作者の方がこのLambda関数を作成した際には1.5GBだったようですが、2016/06/20現在75GBまで増加しています。そのため、わざわざ容量を監視する必要性は薄れていますが、そこは本エントリの主眼ではないので目をつむり、ありがたく利用させていただきます。
コード
実際のコードをGitHubに上げておきました。リポジトリをcloneすれば利用可能です。
リポジトリの構造
apex-check-lambda-capacity/ ├── .apexignore ├── .gitignore ├── .gitmodules ├── README.md ├── functions │ └── check_lambda_capacity │ ├── <snip> ├── infrastructure │ ├── dev │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ └── modules │ ├── cloudwatch │ │ ├── cloudwatch.tf │ │ └── variables.tf │ └── iam │ ├── iam.tf │ └── outputs.tf └── project.json
コードの解説
主要なファイルの内容について以下に解説します。
project.json
{ "name": "apex-check-lambda-capacity", "description": "apex check lambda capacity", "nameTemplate": "{{.Function.Name}}", "handler": "lambda_function.lambda_handler", "memory": 128, "timeout": 5, "runtime": "python", "defaultEnvironment": "dev", "hooks": { "build": "[[ -d placebo ]] || pip install -r requirements.txt -t ./" } }
以前のエントリとほぼ同じ内容ですが、 hooks
が加わっています。ApexにはLifeCycle hookという機能があり、特定のEvent時にShellコマンドを実行可能です。現在サポートされているhookは以下の3つです。
hook | 機能 |
---|---|
build |
run before a function zip is built (use this to compile binaries or transform source) |
deploy |
run before a function is deployed (useful for testing, linting) |
clean |
run after a function is deployed (useful for cleaning up build artifacts) |
今回のLambda関数は標準では含まれていない外部パッケージを利用しているので、デプロイパッケージをzip化する前に pip
コマンドでそれを取得する必要があります。
infrastructure/dev/main.tf
module "iam" { source = "../modules/iam" } module "sns_email_topic" { source = "github.com/deanwilson/tf_sns_email" display_name = "${var.display_name}" email_address = "${var.email_address}" owner = "${var.owner}" stack_name = "${var.stack_name}" } module "cloudwatch" { source = "../modules/cloudwatch" apex_function_check_lambda_capacity = "${var.apex_function_check_lambda_capacity}" lambda_function_role_id = "${module.iam.lambda_function_role_id}" sns_email_topic_arn = "${module.sns_email_topic.arn}" }
主な変更点は module "sns_email_topic"
です。Terraformにはaws_sns_topic_subscription Resourceを利用してSNS Topicの購読ができます。しかし、Email/SMSには対応していません。これらのProtocolは購読の許可をしなければならずTerraformの処理とマッチないためです。
この問題を解決するためにdeanwilson/tf_sns_emailを利用しています。このModuleはaws_cloudformation_stack Resourceを利用してCFn経由でSNS Topicを作成します。CFnの場合はProtocolがEmailの場合でもSNS Topicの購読処理に対応ているのでこの問題を解決することができます。
infrastructure/modules/cloudwatch/cloudwatch.tf
resource "aws_cloudwatch_event_rule" "lambda" { name = "apex-check-lambda-capacity" description = "apex check lambda capacity" schedule_expression = "rate(1 minute)" } resource "aws_cloudwatch_event_target" "lambda" { rule = "${aws_cloudwatch_event_rule.lambda.name}" target_id = "apex-check-lambda-capacity" arn = "${var.apex_function_check_lambda_capacity}" } resource "aws_lambda_permission" "lambda" { statement_id = "AllowExecutionFromCloudWatch" action = "lambda:InvokeFunction" function_name = "${aws_cloudwatch_event_target.lambda.arn}" principal = "events.amazonaws.com" source_arn = "${aws_cloudwatch_event_rule.lambda.arn}" } resource "aws_cloudwatch_metric_alarm" "lambda" { alarm_name = "apex-check-lambda-capacity" alarm_description = "Lambda capacity usage alert" namespace = "lambda" metric_name = "size" statistic = "Average" period = "300" unit = "Bytes" evaluation_periods = "2" threshold = "${var.threshold}" comparison_operator = "GreaterThanOrEqualToThreshold" alarm_actions = ["${var.sns_email_topic_arn}"] }
aws_cloudwatch_event_ruleの schedule_expression
Argumentで定期処理の記述をしています。書式は公式ドキュメントを参照してください。
aws_cloudwatch_metric_alarmでCloudWatch Alarmの設定をしています。内容はLambda関数のリポジトリに含まれているCFnを参考にしました。
コードの実行
以下ではコードを実行する方法について解説します。
Submoduleの取得
Lambda関数をInvokeする前に肝心のLambda関数をGitHubからpullします。Submoduleとして取り込んでいるので以下のコマンドを実行すればOKです。
$ git submodule update --init
ApexのLifeCycle hookで実行できないかと試してみたのですが、そのそもこの時点ではApexから認識できるLambda関数が存在しない状態なのでエラーになってしまいました。残念。
ModuleへのSymlink作成
以下のコマンドを実行してModuleへのSymlinkを infrastructure/dev/.terraform/modules
以下に作成してください。
$ apex infra get
Lambda関数用IAM Roleの作成
続いてLambda関数に割り当てるIAM Roleを作成します。以下のコマンドを実行してください。
# 確認 $ apex infra plan -target=module.iam -var apex_function_check_lambda_capacity=aaa # 実行 $ apex infra apply -target=module.iam -var apex_function_check_lambda_capacity=aaa
以前のエントリを書いた時点では気付かなかったのですが、ApexはTerraformに以下の変数を自動で渡してくれます。
変数 | 内容 |
---|---|
aws_region |
the AWS region name such as us-west-2 |
apex_environment |
the environment name such as prod or stage |
apex_function_role |
the Lambda role ARN |
apex_function_NAME |
Lambda function ARNs by NAME |
しかし、この変数はLambda関数をApex経由でデプロイした後に設定されるようです。この時点ではまだデプロイしてないので、変数が設定されていません。そのため -var
で適当な値を変数に設定しておく必要があります。辛みある。
ちなみに、ApexがTerraformに渡す変数は以下のコマンドで確認可能です( aws_region
は表示されないようです)。
$ apex list --tfvars apex_function_check_lambda_capacity="arn:aws:lambda:ap-northeast-1:************:function:check_lambda_capacity"
Lambda関数のデプロイ
IAM Roleの作成が完了したらLambda関数をデプロイしましょう。
# 確認 $ apex deploy --dry-run # 実行 $ apex deploy
残りのTerraformを実行
この時点ではLambda関数のデプロイが完了しているので infra
サブコマンドにオプションを指定する必要はありません。以下のコマンドを実行するとSNS Topicを作成します。購読用Emailは infrastructure/modules/cloudwatch/variables.tf
に設定してあるので事前に修正してください。
# 確認 $ apex infra plan # 実行 $ apex infra apply
Terraformの実行後該当のEmailにSNS Topicの確認メールが届くはずです。購読してください。
Lambda関数のInvoke
今回のLambda関数はCloudWatch Events Scheduleによって定期的に実行されます。本当に実行されているのかログへの書き込みをもって確認したいので以下のコマンドを実行しておきましょう。
$ apex logs --follow
例えば以下のようなログが表示されます。
/aws/lambda/check_lambda_capacity START RequestId: 47ea14fb-35d8-11e6-b276-1761d2680933 Version: $LATEST /aws/lambda/check_lambda_capacity 9092950 /aws/lambda/check_lambda_capacity {'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '4801e34d-35d8-11e6-9baa-014115757042'}} /aws/lambda/check_lambda_capacity END RequestId: 47ea14fb-35d8-11e6-b276-1761d2680933 /aws/lambda/check_lambda_capacity REPORT RequestId: 47ea14fb-35d8-11e6-b276-1761d2680933 Duration: 69.02 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 47 MB
CloudWatch Metricの出力も見てみましょう。
$ aws cloudwatch get-metric-statistics \ --namespace lambda \ --metric-name size \ --start-time "$(date -v1H '+%Y-%m-%dT%TZ')" \ --end-time "$(date '+%Y-%m-%dT%TZ')" \ --statistics Average \ --period 60 \ --query 'Datapoints[0]' { "Timestamp": "2016-06-19T04:47:00Z", "Average": 9092950.0, "Unit": "Bytes" }
ちゃんとMetricが表示されているようです。やりましたね。
CloudWatch Alarmの確認
正常にCloudWatch Alarmが実行されるか確認してみます。 infrastructure/modules/cloudwatch/variables.tf
の threshold
で閾値を定義しています。1MB(1000000Bytes)に変更してTerraformを再度実行してみます。
$ apex infra plan <snip> ~ module.cloudwatch.aws_cloudwatch_metric_alarm.lambda threshold: "100000000" => "1000000" Plan: 0 to add, 1 to change, 0 to destroy. $ apex infra apply
しばらくすると「[Alert] apex-check-lambda-capacity」という件名のSNSの通知が来ると思います。
まとめ
いかがだったでしょうか。
ApexとTerraformを利用することによりCloudWatch Eventsだけではなく、CloudWatch Events Scheduleの設定もコード化できることを確認しました。
本エントリがみなさんの参考になれば幸いです。